import tkinter as tk
from tkinter import ttk, filedialog
# from PIL import Image, ImageTk  # 需要先安装：pip install pillow
from PIL import Image, ImageDraw, ImageTk
import ctypes
# Win32 API 函数
_user32 = ctypes.windll.user32
MapVirtualKeyW = _user32.MapVirtualKeyW
GetKeyNameTextW = _user32.GetKeyNameTextW

class CircleButton(tk.Canvas):
    """
    自定义圆形按钮：继承自 Canvas，通过画圆和文字来模拟按钮
    支持按下/抬起变色，以及点击回调
    """
    def __init__(self, master, x, y, r, text ,command=None, border_width=2, **kwargs):
        """
        构造函数参数：
        - master: 父容器（通常是顶层窗口 root）
        - x, y: 该按钮左上角在父容器中的坐标（像素）
        - r: 圆形按钮半径（像素）
        - text: 显示在按钮上的文字
        - command: 点击完成后要执行的回调函数（可选）
        - kwargs: 其他传给 Canvas 的参数
        """
        # 初始化 Canvas，大小为 2r×2r，去掉边框 highlightthickness=0
        super().__init__(master, width=2*r, height=2*r, highlightthickness=0, **kwargs)

        self.r = r                   # 保存半径
        self.command = command       # 保存外部回调
        self.is_pressed = False      # 按钮当前是否被按下

        # 在 Canvas 上绘制圆形：(2,2) 到 (2r-2,2r-2)，留 2px 边距
        self.circle = self.create_oval(
            0, 0, 2*r-2, 2*r-2,
            fill='white',          # 默认白底
            outline='black',       # 黑色边框
            width = 2#border_width                # 边框宽度
        )
        # 在圆心绘制文字，字体大小约为半径的 80%
        self.label = self.create_text(
            r, r,                 # 文本中心坐标
            text=text,
            fill='black',         # 默认黑字
            font=('Arial', int(r*0.8), 'bold')
        )
        # 将这个 Canvas 放到父容器的 (x, y) 位置
        self.place(x=x, y=y)

        # 绑定鼠标事件
        # self.bind("<ButtonPress-1>", self._on_press)     # 鼠标左键按下
        # self.bind("<ButtonRelease-1>", self._on_release) # 鼠标左键释放
        # self.bind("<Leave>", self._on_leave)             # 光标移出区域

    def _on_press(self, event):
        """处理按下：设置状态并更新颜色"""
        self.is_pressed = True
        self._update_colors()

    def _on_release(self, event):
        """处理释放：如果之前是按下状态，先恢复颜色再执行命令"""
        if self.is_pressed:
            self.is_pressed = False
            self._update_colors()
            if self.command:
                self.command()

    def _on_leave(self, event):
        """处理光标移出：如果仍处于按下状态，强制恢复"""
        if self.is_pressed:
            self.is_pressed = False
            self._update_colors()

    def _update_colors(self):
        """
        根据 is_pressed 状态切换颜色：
          - 按下时：背景黑、文字白
          - 未按下：背景白、文字黑
        """
        if self.is_pressed:
            self.itemconfig(self.circle, fill='black')
            self.itemconfig(self.label,  fill='white')
        else:
            self.itemconfig(self.circle, fill='white')
            self.itemconfig(self.label,  fill='black')


import tkinter as tk

class RoundedRectButton(tk.Canvas):
    def __init__(self, master, x, y, width, height, radius=10, command=None, bg='white', fg='black', **kwargs):
        """
        圆角矩形按钮类

        参数说明：
        - master: 父控件
        - x, y: 按钮左上角坐标
        - width, height: 按钮的宽高
        - radius: 圆角半径
        - command: 按钮点击回调函数
        - bg: 默认背景颜色
        - fg: 默认文字颜色
        """
        super().__init__(master, width=width, height=height, highlightthickness=0, bg=master["bg"], **kwargs)

        self.command = command
        self.is_pressed = False
        self.radius = radius
        self.bg = bg
        self.fg = fg
        self.pressed_bg = 'black'
        self.pressed_fg = 'white'

        # 绘制圆角矩形
        self.rect = self._draw_rounded_rect(0, 0, width, height, radius, fill=bg, outline="")

        # 没有文字，可以留空或扩展为有文字的按钮
        # self.label = self.create_text(width//2, height//2, text="", fill=fg, font=("Arial", 12))

        self.place(x=x, y=y)

        # self.bind("<ButtonPress-1>", self.on_press)
        # self.bind("<ButtonRelease-1>", self.on_release)
        # self.bind("<Leave>", self.on_leave)

    def _draw_rounded_rect(self, x1, y1, x2, y2, r, **kwargs):
        """
        绘制一个圆角矩形，返回构成该图形的所有图形元素 ID 列表
        """
        elements = []

        # 四个角
        elements.append(self.create_arc(x1, y1, x1+2*r, y1+2*r, start=90, extent=90, style='pieslice', **kwargs))         # 左上
        elements.append(self.create_arc(x2-2*r, y1, x2, y1+2*r, start=0, extent=90, style='pieslice', **kwargs))          # 右上
        elements.append(self.create_arc(x1, y2-2*r, x1+2*r, y2, start=180, extent=90, style='pieslice', **kwargs))        # 左下
        elements.append(self.create_arc(x2-2*r, y2-2*r, x2, y2, start=270, extent=90, style='pieslice', **kwargs))        # 右下

        # 四条边（连接四个角）
        elements.append(self.create_rectangle(x1+r, y1, x2-r, y2, **kwargs))               # 水平中间
        elements.append(self.create_rectangle(x1, y1+r, x2, y2-r, **kwargs))               # 垂直中间

        return elements

    def update_colors(self):
        """
        根据状态改变颜色
        """
        for element in self.rect:
            self.itemconfig(element, fill=self.pressed_bg if self.is_pressed else self.bg)

    def on_press(self, event):
        self.is_pressed = True
        self.update_colors()

    def on_release(self, event):
        if self.is_pressed:
            self.is_pressed = False
            self.update_colors()
            if self.command:
                self.command()

    def on_leave(self, event):
        if self.is_pressed:
            self.is_pressed = False
            self.update_colors()

import tkinter as tk
class TriggerProgressBars:
    """
    用于显示左右扳机进度的竖直方向控件组，包括两个标签和两个竖直进度条。
    """

    def __init__(self, master, x=10, y=160, length=150, height=20):
        """
        构造函数

        :param master: 父容器
        :param x: 组件组左上角 x 坐标
        :param y: 组件组左上角 y 坐标
        :param length: 进度条的高度（竖直方向长度）
        :param height: 进度条的宽度（横向厚度）
        """
        #self.trigger_bars = TriggerProgressBars(self.root, x=105, y=360, length=200, height=40)

        self.Left_right_spacing = 560
        self.lt_var = tk.IntVar(value=0)
        self.rt_var = tk.IntVar(value=0)


        # 左扳机标签和竖直进度条
        tk.Label(master, text="左扳机").place(x=x+9, y=y + length + 10)
        self.lt_bar = ttk.Progressbar(
            master,
            orient="vertical",
            length=length,
            mode="determinate",
            maximum=255,
            variable=self.lt_var
        )
        self.lt_bar.place(x=x + 9, y=y, width=height, height=length)

        # 左扳机顶部显示数值的Label
        self.lt_label = tk.Label(master, text="0")
        self.lt_label.place(x=x + 16, y=y - 20, width=25, height=20)

        # 右扳机标签和竖直进度条
        tk.Label(master, text="右扳机").place(x=x + self.Left_right_spacing , y=y + length + 10)
        self.rt_bar = ttk.Progressbar(
            master,
            orient="vertical",
            length=length,
            mode="determinate",
            maximum=255,
            variable=self.rt_var
        )
        self.rt_bar.place(x=x + self.Left_right_spacing , y=y, width=height, height=length)

        # 右扳机顶部显示数值的Label
        self.rt_label = tk.Label(master, text="0")
        self.rt_label.place(x=x + 6 + self.Left_right_spacing, y=y - 20, width=25, height=20)


    def update(self, lt_value, rt_value):
        """
        更新进度条的值（0~100）

        :param lt_value: 左扳机数值
        :param rt_value: 右扳机数值
        """

        self.lt_var.set(lt_value)
        self.rt_var.set(rt_value)

        # 更新顶部显示数值的Label文本
        self.lt_label.config(text=str(lt_value))
        self.rt_label.config(text=str(rt_value))

def make_ring_image(outer_r, ring_width, color):
    """
    生成一个透明背景的圆环图像（ImageTk.PhotoImage）。
    - outer_r: 外环半径
    - ring_width: 环厚度（像素）
    - color: 环的颜色
    """
    size = (outer_r*2, outer_r*2)
    img = Image.new("RGBA", size, (0,0,0,0))
    draw = ImageDraw.Draw(img)
    # 外圆
    draw.ellipse((0,0, size[0]-1, size[1]-1), outline=color, width=ring_width)
    return ImageTk.PhotoImage(img)

def make_circle_image(r, fill):
    """
    生成一个透明背景的实心圆图像（ImageTk.PhotoImage）。
    - r: 半径
    - fill: 填充颜色
    """
    size = (r*2, r*2)
    img = Image.new("RGBA", size, (0,0,0,0))
    draw = ImageDraw.Draw(img)
    draw.ellipse((0,0, size[0]-1, size[1]-1), fill=fill)
    return ImageTk.PhotoImage(img)


class JoystickRingIndicator:
    """
    不使用 Canvas，在 root 上显示：
    - 一个固定不动的外环
    - 一个可移动的内圆指示点
    """
    def __init__(self, master,
                 center_x, center_y,
                 outer_radius=30, ring_width=4, ring_color="#000000",
                 inner_radius=10, default_color="#808080", active_color="#000000",
                 max_offset=50):
        """
        :param master: 父容器（如 root）
        :param center_x, center_y: 摇杆中心坐标
        :param outer_radius: 外环半径
        :param ring_width: 外环线宽
        :param ring_color: 外环颜色
        :param inner_radius: 内圆半径
        :param default_color: 内圆默认颜色
        :param active_color: 内圆激活颜色
        :param max_offset: 内圆最大偏移（像素）
        """
        self.master = master
        self.cx = center_x
        self.cy = center_y
        self.outer_r = outer_radius
        self.inner_r = inner_radius
        self.max_offset = max_offset

        self.default_color = default_color
        self.active_color  = active_color
        self.current_color = default_color

        # 生成并显示外环
        self.ring_image = make_ring_image(self.outer_r, ring_width, ring_color)
        self.ring_label = tk.Label(master, image=self.ring_image, bd=0)
        # place 的坐标要左上角
        self.ring_label.place(x=self.cx - self.outer_r, y=self.cy - self.outer_r)
        self.ring_label.lift()

        # 生成内圆图像与 Label
        self.inner_image = make_circle_image(self.inner_r, self.current_color)
        self.inner_label = tk.Label(master, image=self.inner_image, bd=0)
        # 初始放在中心
        self.inner_label.place(x=self.cx - self.inner_r, y=self.cy - self.inner_r)
        self.inner_label.lift()

    def update_raw(self, raw_x, raw_y):
        """
        更新内圆位置及颜色：
        - raw_x, raw_y: 原始摇杆数值范围 -32768~32767
        """
        # 归一化到 [-1,1]
        nx = max(min(raw_x / 32767, 1), -1)
        ny = max(min(raw_y / 32767, 1), -1)

        # 计算像素偏移
        dx = nx * self.max_offset
        dy = ny * self.max_offset

        # 移动内圆
        new_x = int(self.cx + dx - self.inner_r)
        new_y = int(self.cy + dy - self.inner_r)
        self.inner_label.place(x=new_x, y=new_y)

        # 颜色切换：只要不全为0则激活色，否则默认色
        target_color = self.active_color if (raw_x != 0 or raw_y != 0) else self.default_color
        if target_color != self.current_color:
            self.current_color = target_color
            # 重绘内圆图像并更新
            self.inner_image = make_circle_image(self.inner_r, self.current_color)
            self.inner_label.config(image=self.inner_image)
def get_key_name_from_vk(vk_code):
    """
    给定 Windows 虚拟键码（event.keycode），
    返回系统映射的按键名称，例如 "A", "F1", "Left"。
    """
    # MAPVK_VK_TO_VSC = 0
    scancode = MapVirtualKeyW(vk_code, 0)
    # lParam 高 16 位存放扫描码，低位可为 0
    lParam = scancode << 16
    # 创建缓冲区
    buf = ctypes.create_unicode_buffer(32)
    # 调用 Win32 GetKeyNameTextW
    GetKeyNameTextW(lParam, buf, ctypes.sizeof(buf))
    return buf.value

class KeyMappingLabel(tk.Label):
    def __init__(self, master, action_name, initial_key, mapping_enabled_var, **kwargs):
        """
        :param master: 父容器
        :param action_name: 功能名，如 "左摇杆上"
        :param initial_key: 初始映射键名，如 "W"
        :param mapping_enabled_var: tk.BooleanVar，True=映射已启动（此时禁止修改）
        :param kwargs: 其他传给 tk.Label 的参数
        """
        self.action_name = action_name
        self.current_key = initial_key
        self.mapping_enabled_var = mapping_enabled_var

        # 监听映射开关变化，动态更新文字颜色
        mapping_enabled_var.trace_add("write", self._on_mapping_toggle)

        # 初始化 Label 文本和样式
        super().__init__(master,
                         text=self._make_text(),
                         fg="black" if not mapping_enabled_var.get() else "gray",
                         cursor="hand2",
                         **kwargs)

        # 点击事件绑定
        self.bind("<Button-1>", self._on_click)

    def _make_text(self):
        return f"{self.action_name}: {self.current_key or '<空>'}"

    def _on_mapping_toggle(self, *args):
        """映射开关切换时调用，改变文字颜色"""
        if self.mapping_enabled_var.get():
            self.config(fg="gray")
        else:
            self.config(fg="black")

    def _on_click(self, event):
        if self.mapping_enabled_var.get():
            return

        # 创建模态弹窗
        prompt = tk.Toplevel(self)
        prompt.title("重新映射")
        prompt.resizable(False, False)

        # 弹窗内容
        tk.Label(prompt, text="请按下要映射的键\nEsc 取消  Del 清空",
                 padx=20, pady=10).pack()

        # 让 prompt 先布局好自身大小
        prompt.update_idletasks()

        # 计算主窗口和弹窗的尺寸及位置
        master = self.winfo_toplevel()  # 获取最顶层窗口
        # 强制主窗口刷新并获取尺寸
        master.update_idletasks()
        mw = master.winfo_width()
        mh = master.winfo_height()
        mx = master.winfo_rootx()
        my = master.winfo_rooty()

        pw = prompt.winfo_width()
        ph = prompt.winfo_height()

        # 计算弹窗左上角应放置的位置，使其在主窗口中心
        px = mx + (mw - pw) // 2
        py = my + (mh - ph) // 2

        # 设置弹窗几何，并置顶/取焦
        prompt.geometry(f"{pw}x{ph}+{px}+{py}")
        prompt.transient(master)   # 窗口总在 master 之上
        prompt.grab_set()
        prompt.focus_force()

        prompt.bind_all("<KeyPress>", on_key_press := lambda evt: self._on_key_probe(evt, prompt))

    # 键处理
    def _on_key_probe(self, evt, prompt):
        # 1) 取消
        if evt.keysym == "Escape":
            prompt.destroy()
            return

        # 2) 清空
        if evt.keysym == "Delete":
            self.current_key = "NULL"
            self.config(text=self._make_text())
            prompt.destroy()
            return

        # 3) 尝试最简单的可打印字符
        char = evt.char
        if char and char.isprintable() and char != "?":
            mapped = char.upper()
        else:
            # 再试 keysym
            if evt.keysym and evt.keysym != "?":
                mapped = evt.keysym.upper()
            else:
                # 最后用 Win32 API 读取虚拟键码
                mapped = get_key_name_from_vk(evt.keycode).upper()

        self.current_key = mapped
        self.config(text=self._make_text())
        prompt.destroy()



    def get(self):
        """返回当前映射键名"""
        return self.current_key

class App:
    """
    主应用类：封装整个 GUI 界面的创建与事件逻辑
    """
    def __init__(self):
        # 创建顶层窗口
        self.root = tk.Tk()
        self.root.title("键盘映射手柄 V1.7")
        # 固定窗口大小，不允许用户拉伸
        self.root.resizable(False, False)
        # 窗口初始尺寸（宽 x 高）
        self.root.geometry("800x600")

        # 尝试加载左上角图标
        self._load_icon()
        # 构建界面所有控件
        self._build_widgets()


    def _load_icon(self):
        """
        设置窗口左上角图标：
        优先加载 .ico（Windows），失败时尝试用 PNG
        """
        try:
            self.root.iconbitmap("键盘映射手柄.ico")
        except Exception:
            icon = tk.PhotoImage(file="键盘映射手柄.png")
            self.root.iconphoto(False, icon)

    def _build_widgets(self):
        """创建并布局所有子控件"""

        # -- 1. 映射状态文字 --
        # 用 StringVar 绑定动态文字
        self.status_var = tk.StringVar(value="未启动")
        tk.Label(self.root, text="映射状态：").place(x=10, y=10)
        tk.Label(self.root, textvariable=self.status_var).place(x=80, y=10)

        # -- 2. 启用映射复选框 --
        self.enable_var = tk.BooleanVar(value=False)
        self.Checkbutton = tk.Checkbutton(
            self.root,
            text="启用映射",
            variable=self.enable_var,
            command=self._on_toggle  # 状态改变时回调
        )
        self.Checkbutton.place(x=10, y=40)

        # -- 3. 配置模式下拉菜单 --
        tk.Label(self.root, text="配置模式：").place(x=10, y=80)
        self.combo_var = tk.StringVar(value="简单")
        self.combo_box = ttk.Combobox(
            self.root,
            values=["简单", "专家", "大师"],
            textvariable=self.combo_var,
            state="readonly",
            width = 5
        )
        self.combo_box.place(x=80, y=80)

        # 添加扳机进度条组件
        # self.trigger_bars = TriggerProgressBars(self.root, x=10, y=160)
        self.trigger_bars = TriggerProgressBars(self.root, x=95, y=330, length=200, height=40)
        #self.trigger_bars.update(180,100)

        # -- 4. 手柄背景图（居中显示） --
        # 加载并缩放图片到 474×318
        img = Image.open("Xbox.png").resize((474, 318), Image.Resampling.LANCZOS)
        photo = ImageTk.PhotoImage(img)
        lbl = tk.Label(self.root, image=photo)
        lbl.image = photo  # 保留引用防止被回收
        # relx/rely=0.5 表示相对父窗口中心，anchor="center" 锚点为中心
        lbl.place(relx=0.5, rely=0.7, anchor="center")

        # -- 5. 圆形按钮：示例 A 键 --
        CircleButton(
            self.root,
            x=501, y=370,   # 坐标
            r=19,           # 半径
            text="A",      # 显示文字
            border_width = 0
            #command=self._on_a  # 点击回调
        )

        CircleButton(
            self.root,
            x=501, y=306,  # 坐标
            r=19,  # 半径
            text="Y",  # 显示文字
            border_width=0
            #command=self._on_ab  # 点击回调
        )

        CircleButton(
            self.root,
            x=532, y=337,  # 坐标
            r=19,  # 半径
            text="B",  # 显示文字
            border_width=0
            #command=self._on_ab  # 点击回调
        )

        CircleButton(
            self.root,
            x=470, y=337,  # 坐标
            r=19,  # 半径
            text="X",  # 显示文字
            border_width=0
            #command=self._on_ab  # 点击回调
        )


        CircleButton(
            self.root,
            x=355, y=346,  # 坐标
            r=12,  # 半径
            text="",  # 显示文字
            border_width=3
            # command=self._on_ab  # 点击回调
        )

        CircleButton(
            self.root,
            x=423, y=346,  # 坐标
            r=12,  # 半径
            text="",  # 显示文字
            border_width = 3
            # command=self._on_ab  # 点击回调
        )

        # 创建LB键按钮
        btn_LB = RoundedRectButton(self.root,x=235, y=230, width=60, height=30,radius=10, command=0)

        # 创建RB键按钮
        btn_RB = RoundedRectButton(self.root,x=500, y=230, width=60, height=30,radius=10, command=0)

        # 创建上方向键按钮
        btn_up = RoundedRectButton(self.root,x=331, y=401, width=16, height=23,radius=0, command=0)

        # 创建下方向键按钮
        btn_down = RoundedRectButton(self.root,x=331, y=440, width=16, height=23,radius=0, command=0)

        # 创建左方向键按钮
        btn_left = RoundedRectButton(self.root,x=308, y=424, width=23, height=16,radius=0, command=0)

        # 创建右方向键按钮
        btn_right = RoundedRectButton(self.root,x=347, y=424, width=23, height=16,radius=0, command=0)


        # 创建左右两个指示器
        self.left_ind = JoystickRingIndicator(self.root, 281, 356)
        self.right_ind = JoystickRingIndicator(self.root, 460, 430)

        # 创建几个映射标签
        km1 = KeyMappingLabel(self.root, "左摇杆上", "W", mapping_enabled_var=self.enable_var)
        km1.pack(pady=5)
        km2 = KeyMappingLabel(self.root, "左摇杆下", "S", mapping_enabled_var=self.enable_var)
        km2.pack(pady=5)
        km3 = KeyMappingLabel(self.root, "按钮A", "J", mapping_enabled_var=self.enable_var)
        km3.pack(pady=5)
    # # -- 6. 底部操作按钮：保存 & 退出 --
        # frm = tk.Frame(self.root)
        # tk.Button(
        #     frm,
        #     text="保存配置",
        #     command=self._on_save
        # ).pack(side="left", padx=5)
        # tk.Button(
        #     frm,
        #     text="退出",
        #     command=self.root.destroy
        # ).pack(side="left", padx=5)
        # frm.place(x=10, y=520)

    def _on_toggle(self):
        """
        处理 '启用映射' 复选框切换：
        同步更新状态文字
        """
        self.status_var.set("已启动" if self.enable_var.get() else "未启动")

    def _on_a(self):
        """A 按钮点击回调"""
        print("A 键被按下")

    def _on_save(self):
        """保存配置回调（可扩展为写 ini/JSON）"""
        print("保存配置：启用映射 =", self.enable_var.get(),
              "模式 =", self.combo_var.get())

    def run(self):
        """启动 Tkinter 主事件循环"""
        self.root.mainloop()

def main():
    """程序入口"""
    app = App()
    app.run()

if __name__ == "__main__":

    main()
